package com.greyshark.lib.bluetooth

import android.bluetooth.BluetoothAdapter
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.ServiceConnection
import android.os.Build
import android.os.IBinder
import androidx.annotation.IdRes
import com.greyshark.lib.bluetooth.callback.CommonResultInfoCallback
import com.greyshark.lib.bluetooth.callback.query.BatteryInfoCallback
import com.greyshark.lib.bluetooth.callback.query.BloodOxygenCallback
import com.greyshark.lib.bluetooth.callback.query.DailyInfoCallback
import com.greyshark.lib.bluetooth.callback.query.DaySleepCallback
import com.greyshark.lib.bluetooth.callback.query.DeviceInfoCallback
import com.greyshark.lib.bluetooth.callback.query.EffectiveStandCallback
import com.greyshark.lib.bluetooth.callback.query.HealthInfoCallback
import com.greyshark.lib.bluetooth.callback.query.HeartRateCallback
import com.greyshark.lib.bluetooth.callback.query.LightingInfoCallback
import com.greyshark.lib.bluetooth.callback.query.LightingNotDisturbInfoCallback
import com.greyshark.lib.bluetooth.callback.query.NightSleepCallback
import com.greyshark.lib.bluetooth.callback.query.RealTimeSportCallback
import com.greyshark.lib.bluetooth.callback.query.SportRecordCallback
import com.greyshark.lib.bluetooth.callback.query.SupportFunctionCallback
import com.greyshark.lib.bluetooth.callback.query.TemperatureCallback
import com.greyshark.lib.bluetooth.callback.query.UserInfoCallback
import com.greyshark.lib.bluetooth.core.BleConst
import com.greyshark.lib.bluetooth.entity.ScanDeviceEntity
import com.greyshark.lib.bluetooth.entity.query.DeviceInfoEntity
import com.greyshark.lib.bluetooth.entity.query.LightingInfoEntity
import com.greyshark.lib.bluetooth.entity.query.UserInfoEntity
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_BATTERY_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_BLOOD_OXYGEN
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_DAILY_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_DAY_SLEEP_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_DEVICE_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_EFFECTIVE_STANDING_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_HEALTH_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_HEART_RATE
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_LIGHTING_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_LIGHTING_NOT_DISTURB_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_NIGHT_SLEEP_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_REALTIME_SPORT_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_SPORT_RECORD_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_SUPPORT_FUNCTION
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_TEMPERATURE
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_QUERY_USER_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_SYNC_APP_STATUS
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_SYNC_LIGHTING
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_SYNC_LIGHTING_NOT_DISTURB_INFO
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_SYNC_SPORT_STATUS
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_SYNC_TIME
import com.greyshark.lib.bluetooth.parse.KEY_TIMEOUT_SYNC_USER_INFO
import com.greyshark.lib.bluetooth.parse.PacketParse
import com.greyshark.lib.bluetooth.receive.BluetoothReceiver
import com.greyshark.lib.bluetooth.utils.ByteHexUtils
import com.greyshark.lib.bluetooth.utils.LogUtils
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach

class ControlCenterClient {

  companion object {
    val instance: ControlCenterClient by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
      ControlCenterClient()
    }
  }

  private val tag = ControlCenterClient::class.java.simpleName
  private var isInit = false
  private lateinit var bluetoothService: BluetoothKtService
  internal var context: Context? = null

  internal var deviceName: String = ""

  internal var deviceAddress: String = ""

  private var bluetoothReceiver: BluetoothReceiver? = null

  var notificationTitle: String = ""
  var notificationBody: String = ""
  var notificationAppIconRes: Int = -1

  //指令延时时间 (0,200] 默认5毫秒
  var packetDelayTime: Long = 10L

  private lateinit var initSuccessCallback: (() -> Unit)
  private lateinit var initFailureCallback: ((Throwable) -> Unit)
  fun initialize(
    context: Context,
    notificationTitle: String = "",
    notificationBody: String = "",
    @IdRes notificationIconRes: Int = -1,
    packetDelayTime: Long = BleConst.CMD_THREAD_SLEEP_TIME,
    initSuccess: () -> Unit,
    initFailure: (e: Throwable) -> Unit
  ): Unit {
    LogUtils.initLogger(context = context, isWriteLog = true, isRelease = false)
    LogUtils.i(tag, "client initialize")

    if (this.isInit && this.context != null) {
      this.context!!.unbindService(serviceConnection)
      if (bluetoothReceiver != null) {
        this.context?.unregisterReceiver(bluetoothReceiver)
        bluetoothReceiver = null
      }
    }

    this.isInit = true
    this.context = context
    this.notificationTitle = notificationTitle
    this.notificationBody = notificationBody
    this.notificationAppIconRes = notificationIconRes
    this.packetDelayTime = packetDelayTime
    this.initSuccessCallback = initSuccess
    this.initFailureCallback = initFailure

    //绑定服务
    val intent = Intent(context, BluetoothKtService::class.java)
    this.context!!.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)

    //注册广播
    if (bluetoothReceiver == null) {
      bluetoothReceiver = BluetoothReceiver()
      val bleFilter = IntentFilter()
      // 监听蓝牙关闭和打开的状态
      bleFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        this.context!!.registerReceiver(bluetoothReceiver, bleFilter, Context.RECEIVER_EXPORTED)
      } else {
        this.context!!.registerReceiver(bluetoothReceiver, bleFilter)
      }
    }
  }

  private val serviceConnection = object : ServiceConnection {
    override fun onServiceConnected(
      name: ComponentName?,
      service: IBinder?
    ) {
      LogUtils.i(tag, "client onServiceConnected")
      if (service is BluetoothKtService.BluetoothBinder) {
        val appBinder: BluetoothKtService.BluetoothBinder = service
        bluetoothService = appBinder.service
        initSuccessCallback.invoke()

      } else {
        initFailureCallback.invoke(Throwable("service is not BluetoothKtService"))
      }
    }

    override fun onServiceDisconnected(
      name: ComponentName?
    ) {
      LogUtils.i(tag, "client onServiceDisconnected")
      initFailureCallback.invoke(Throwable("service onServiceDisconnected running"))
      disconnect()
      release()
      context?.let {
        initialize(
          context = it,
          initSuccess = {},
          initFailure = {},
        )
      }
    }
  }

  public fun isServiceRunning(): Boolean {
    //后期实现此逻辑
    return true
  }

  public fun startScan(timeoutMillis: Long): Flow<ScanDeviceEntity> {
    LogUtils.i(tag, "client startScan")
    if (!::bluetoothService.isInitialized) {
      return MutableSharedFlow()
    }
    return bluetoothService.startScan(timeoutMillis)
  }

  public fun stopScan() {
    LogUtils.i(tag, "client stopScan")
    if (!::bluetoothService.isInitialized) {
      return
    }
    bluetoothService.stopScan()
  }

  public fun isConnect(): Boolean {
    if (!::bluetoothService.isInitialized) {
      return false
    }
    return bluetoothService.isConnect
  }

  public fun connect(name: String, address: String) {
    LogUtils.i(tag, "client connect")
    if (!::bluetoothService.isInitialized) {
      return
    }
    this.deviceName = name
    this.deviceAddress = address
    bluetoothService.connect(name, address)
  }

  public fun disconnect() {
    LogUtils.i(tag, "client disconnect")
    if (!::bluetoothService.isInitialized) {
      return
    }
    bluetoothService.disconnect(true)
  }

  internal fun chanceMTU(size: Int) {
    bluetoothService.chanceMTU(size)
  }

  public fun sendCommand(data: ByteArray, isAddInQueue: Boolean = true) {
    LogUtils.i(tag, "send command is ${ByteHexUtils.instance.bytesToHexString(data)}")
    if (!::bluetoothService.isInitialized) {
      return
    }
    if (isAddInQueue) {
      bluetoothService.enqueueCommand(data)
    } else {
      bluetoothService.sendCommand(data)
    }
  }

  public fun release() {
    if (!::bluetoothService.isInitialized) {
      return
    }
    bluetoothService.release()
  }

  public fun connectStateFlowable(timeoutMillis: Long = 10_000L): SharedFlow<BleConnectEvent> {
//    if (!::bluetoothService.isInitialized) {
//      return MutableSharedFlow()
//    }
    return connectEventFlow
  }

  internal val _connectEventFlow = MutableSharedFlow<BleConnectEvent>(
    replay = 0,
    extraBufferCapacity = 10
  )
  val connectEventFlow = _connectEventFlow.asSharedFlow()

  //todo test scan function
  private suspend fun testScan() {
    ControlCenterClient.instance.startScan(30_000L)
      .catch { it ->
        it.message
      }.onCompletion {

      }.collect { it ->
        it.name
      }
  }


  //todo test connect function
  public suspend fun testConnect() {
    ControlCenterClient.instance.connectStateFlowable().onEach { it ->
      LogUtils.i(tag, "connect event is ${it.javaClass.simpleName}")
      when (it) {
        is BleConnectEvent.ConnectTimeOutEvent -> {}
        is BleConnectEvent.ConnectDeviceGattErrorEvent -> {}
        is BleConnectEvent.ConnectDeviceNotFoundEvent -> {}
        is BleConnectEvent.ConnectDiscoverServiceTimeOutEvent -> {}
        is BleConnectEvent.ConnectParamIllegalEvent -> {}
        is BleConnectEvent.ConnectUnknowErrorEvent -> {}
        is BleConnectEvent.ConnectedEvent -> {}
        is BleConnectEvent.ConnectingEvent -> {}
        is BleConnectEvent.DisConnectedEvent -> {}
        is BleConnectEvent.DisConnectingEvent -> {}
        is BleConnectEvent.PermissionDeniedEvent -> {}
        else -> {}
      }
    }.collect()
  }

  //todo query device info
  fun testQuery() {
    ControlCenterClient.instance.queryDeviceInfo(object : DeviceInfoCallback {
      override fun onTimeout() {}

      override fun onError(message: String) {}

      override fun onData(info: DeviceInfoEntity) {}
    })
  }

  //region command and packet
  fun queryDeviceInfo(callback: DeviceInfoCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      deviceInfoCallback = callback
      deviceInfoQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_DEVICE_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryDeviceInfo())
  }

  fun queryBatteryInfo(callback: BatteryInfoCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      batteryInfoCallback = callback
      batteryInfoQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_BATTERY_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryDeviceBattery())
  }

  fun queryUserInfo(callback: UserInfoCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      userInfoCallback = callback
      userInfoQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_USER_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryUserInfo())
  }

  fun openRealTimeSportReport(
    open: Boolean,
    callback: RealTimeSportCallback?,
    timeout: Long = 5_000L
  ) {
    PacketParse.instance.apply {
      realTimeSportCallback = callback
      realTimeSportQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_REALTIME_SPORT_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryRealTimeSportInfo(open))
  }

  fun openHeartRateReport(open: Boolean, callback: HeartRateCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      heartRateCallback = callback
      heartRateQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_HEART_RATE, timeout)
    }
    sendCommand(ProtocolCommand.queryHeartRateInfo(open),false)
  }

  fun openBloodOxygenReport(open: Boolean, callback: BloodOxygenCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      bloodOxygenCallback = callback
      bloodOxygenQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_BLOOD_OXYGEN, timeout)
    }
    sendCommand(ProtocolCommand.queryBloodOxygenInfo(open),false)
  }

  fun openTemperatureReport(open: Boolean, callback: TemperatureCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      temperatureCallback = callback
      temperatureQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_TEMPERATURE, timeout)
    }
    sendCommand(ProtocolCommand.queryTemperatureInfo(open),false)
  }

  fun querySupportFunction(callback: SupportFunctionCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      supportFunctionCallback = callback
      supportFunctionQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_SUPPORT_FUNCTION, timeout)
    }
    sendCommand(ProtocolCommand.querySupportFunction())
  }

  fun queryHealthInfo(time: Long, callback: HealthInfoCallback?, timeout: Long = 15_000L) {
    PacketParse.instance.apply {
      healthInfoCallback = callback
      healthInfoQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_HEALTH_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryHealthInfo(time))
  }

  fun queryDailyInfo(time: Long, callback: DailyInfoCallback?, timeout: Long = 15_000L) {
    PacketParse.instance.apply {
      dailyInfoCallback = callback
      dailyInfoQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_DAILY_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryDailyInfo(time))
  }

  fun queryLightingInfo(callback: LightingInfoCallback?, timeout: Long = 15_000L) {
    PacketParse.instance.apply {
      lightingInfoCallback = callback
      lightingInfoQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_LIGHTING_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryLightingSettings())
  }

  fun queryNightSleepInfo(time: Long, callback: NightSleepCallback?, timeout: Long = 15_000L) {
    PacketParse.instance.apply {
      nightSleepCallback = callback
      nightSleepQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_NIGHT_SLEEP_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryNightTimeSleepInfo(time))
  }

  fun queryDaySleepInfo(time: Long, callback: DaySleepCallback?, timeout: Long = 15_000L) {
    PacketParse.instance.apply {
      daySleepCallback = callback
      daySleepQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_DAY_SLEEP_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryDayTimeSleepInfo(time))
  }

  fun queryEffectiveStandInfo(time: Long, c: EffectiveStandCallback?, timeout: Long = 15_000L) {
    PacketParse.instance.apply {
      effectiveStandCallback = c
      effectiveStandQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_EFFECTIVE_STANDING_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryEffectStandInfo(time))
  }

  fun querySportRecordInfo(time: Long, callback: SportRecordCallback?, timeout: Long = 15_000L) {
    PacketParse.instance.apply {
      sportRecordCallback = callback
      sportRecordQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_SPORT_RECORD_INFO, timeout)
    }
    sendCommand(ProtocolCommand.querySportRecordInfo(time))
  }

  fun syncLightingNotDisturbInfo(switch: Boolean = false,startHour: Int,startMin: Int,endHour: Int,endMin: Int,
    callback: CommonResultInfoCallback?,
    timeout: Long = 5_000L
  ) {
    PacketParse.instance.apply {
      lightingNotDisturbSyncCallback = callback
      lightingNotDisturbSyncTimeout = timeout
      startTimeout(KEY_TIMEOUT_SYNC_LIGHTING_NOT_DISTURB_INFO, timeout)
    }
    sendCommand(ProtocolCommand.syncLightingNotDisturbInfo(switch = switch,startHour=startHour,startMin=startMin,endHour=endHour,endMin=endMin),false)
  }

  fun queryLightingNotDisturbInfo(callback: LightingNotDisturbInfoCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      lightingNotDisturbInfoCallback = callback
      lightingNotDisturbInfoQueryTimeout = timeout
      startTimeout(KEY_TIMEOUT_QUERY_LIGHTING_NOT_DISTURB_INFO, timeout)
    }
    sendCommand(ProtocolCommand.queryLightingNotDisturbInfo())
  }

  fun syncTime(
    time: Long,
    timeZome: Int,
    callback: CommonResultInfoCallback?,
    timeout: Long = 5_000L
  ) {
    PacketParse.instance.apply {
      timeSyncCallback = callback
      timeSyncTimeout = timeout
      startTimeout(KEY_TIMEOUT_SYNC_TIME, timeout)
    }
    sendCommand(ProtocolCommand.pushTimeInfo(time, timeZome))
  }

  fun syncLighting(
    entity: LightingInfoEntity,
    callback: CommonResultInfoCallback?,
    timeout: Long = 5_000L
  ) {
    PacketParse.instance.apply {
      lightingSyncCallback = callback
      lightingSyncTimeout = timeout
      startTimeout(KEY_TIMEOUT_SYNC_LIGHTING, timeout)
    }
    sendCommand(ProtocolCommand.syncLightingInfo(entity),false)
  }

  fun syncUserInfo(entity: UserInfoEntity, c: CommonResultInfoCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      userInfoSyncCallback = c
      userInfoSyncTimeout = timeout
      startTimeout(KEY_TIMEOUT_SYNC_USER_INFO, timeout)
    }
    sendCommand(
      ProtocolCommand.pushUserInfo(
        entity.height,
        entity.weight,
        entity.sex,
        entity.birthday
      )
    )
  }

  fun syncUIStatus(appStatus: Boolean, c: CommonResultInfoCallback?, timeout: Long = 5_000L) {
    PacketParse.instance.apply {
      appStatusSyncCallback = c
      appStatusSyncTimeout = timeout
      startTimeout(KEY_TIMEOUT_SYNC_APP_STATUS, timeout)
    }
    sendCommand(ProtocolCommand.pushAppRunningStatus(appStatus))
  }

  fun chanceSportStatus(
    sportType: Int,
    sportStatus: Int,
    time: Long,
    callback: CommonResultInfoCallback?,
    timeout: Long = 5_000L
  ) {
    PacketParse.instance.apply {
      sportStatusSyncCallback = callback
      sportStatusSyncTimeout = timeout
      startTimeout(KEY_TIMEOUT_SYNC_SPORT_STATUS, timeout)
    }
    sendCommand(ProtocolCommand.pushSportInfo(sportType, sportStatus, time),false)
  }

  //重启设备
  fun reboot() {
    sendCommand(ProtocolCommand.reboot(),false)
  }

  //恢复出厂设置
  fun factoryReset() {
    sendCommand(ProtocolCommand.factoryReset(),false)
  }
}